#include "object.h"
#include <QMouseEvent>
#include <qmath.h>

#ifndef GL_MULTISAMPLE
#define GL_MULTISAMPLE  0x809D
#endif

#define SQUARE_SIZE      1.5
#define OFF_AXIS_RENDER  false
#define ORTHO_PROJECTION true

 // these actually half of the sizes
#define TEXSQAURE_SIZE   50
#define ORTHO_WIDTH      400
#define ORTHO_HEIGHT     300
#define CM_STRING0       "textures/dblue_xcorner.png"
#define CM_STRING1       "textures/green_xcorner.png"
#define CM_STRING2       "textures/lblue_xcorner.png"
#define CM_STRING3       "textures/orange_xcorner.png"
#define CM_STRING4       "textures/pink_xcorner.png"
#define CM_STRING5       "textures/purple_xcorner.png"
#define CM_STRING6       "textures/red_xcorner.png"
#define CM_STRING7       "textures/yellow_xcorner.png"
#define CM_STRING8       "textures/y_xcorner.png"


MyGLObject::MyGLObject(bool isLeft, Camera *camera, QWidget *parent)
    : QGLWidget( parent, 0, Qt::FramelessWindowHint )
{
    _y_offset = 0;
    setFixedSize( WIDTH, HEIGHT );
    _obj_coord.x = 0;
    _obj_coord.y = 0;
    _obj_coord.z = 0;

    _isleft = isLeft;
    _camera = camera;
    //_step = SQUARE_SIZE5f;

    qsrand( 0 );
    for( int i = 0; i < 200; i ++ )
    {
        _spheres[i] = new Mysphere;
        _spheres[i]->qobj = gluNewQuadric();
        _spheres[i]->p.x = (float)( qrand() % 800 ) / 100 - 4;
        _spheres[i]->p.y = (float)( qrand() % 800 ) / 100 - 4;
        _spheres[i]->p.z = (float)( qrand() % 800 ) / 100 - 4;
    }
    _main_sphere = new Mysphere;
    _main_sphere->qobj = gluNewQuadric();
    _main_sphere->p = _obj_coord;

}

MyGLObject::~MyGLObject()
{
    qDebug() << "MyGLObject: Deleting the 3D object";
    for( int i = 0; i < 50; i ++ )
    {
        delete _spheres[i];
    }
    qDebug() << "MyGLObject: Deleted";
}

void MyGLObject::initializeGL()
{
    loadGLTextures();
    glEnable(GL_TEXTURE_2D);
    this->makeCurrent();
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    //glEnable(GL_CULL_FACE); // disable here because in a reversed matrix the culling will go wrong
    glShadeModel(GL_SMOOTH);
    //glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT1);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
    static GLfloat lightAmbient[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
    static GLfloat lightColor[4] = { 1.0, 1.0, 0.0, 0.5 };
    static GLfloat lightPosition[4] = { 0, 0, 0, 1 };
    glLightfv(GL_LIGHT1, GL_DIFFUSE, lightColor );
    glLightfv(GL_LIGHT1, GL_AMBIENT, lightAmbient );
    glLightfv(GL_LIGHT1, GL_POSITION, lightPosition );
}

void MyGLObject::resizeGL( int width, int height )
{
    int left_flag = _isleft ? -1 : 1;
    this->makeCurrent();
    if (height==0)								// Prevent A Divide By Zero By
    {
        height=1;								// Making Height Equal One
    }

    glViewport( 0, 0, width,height );						// Reset The Current Viewport

    glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
    glLoadIdentity();								// Reset The Projection Matrix

    glScaled( -1, 1, 1 ); // horizontally reverse the projection


    double focal   = sqrt( _camera->vp.x * _camera->vp.x
                           + _camera->vp.y * _camera->vp.y
                           + _camera->vp.z * _camera->vp.z );
    double ratio   = _camera->screenwidth/(GLfloat)_camera->screenheight;
    double radians = _camera->aperture * M_PI / 360.0 ;
    double wd2     = _camera->fnear * tan( radians );
    double ndfl    = _camera->fnear / focal;

    double left    = - ratio * wd2 + left_flag * 0.5 * _camera->eyesep * ndfl;
    double right   =   ratio * wd2 + left_flag * 0.5 * _camera->eyesep * ndfl;
    double top     =   wd2;
    double bottom  = - wd2;

    if( OFF_AXIS_RENDER )
        glFrustum( left, right, bottom, top,  _camera->fnear, _camera->ffar );
    else if( ORTHO_PROJECTION )
    {
        glOrtho( -400, 400, -300, 300, 0, 1000  );
    }
    else
        gluPerspective( _camera->aperture, ratio, _camera->fnear, _camera->ffar );
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

}

void MyGLObject::paintGL()
{
    //Utility::PrintCamera(_camera);
    this->makeCurrent();
    int left_flag = _isleft ? -1 : 1;
    XYZ r;
    /* Derive the two eye positions */
    CROSSPROD(_camera->vd, _camera->vu, r);
    Utility::Normalise(&r);
    r.x *= left_flag * _camera->eyesep * 0.5;
    r.y *= left_flag * _camera->eyesep * 0.5;
    r.z *= left_flag * _camera->eyesep * 0.5;

    glMatrixMode(GL_MODELVIEW);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    if( !ORTHO_PROJECTION )
    {
        gluLookAt(_camera->vp.x + r.x,_camera->vp.y + r.y,_camera->vp.z + r.z,
                  0, 0, 0,
                  _camera->vu.x,_camera->vu.y,_camera->vu.z);
    }
    else
    {
        gluLookAt(_camera->vp.x, _camera->vp.y,_camera->vp.z,
                  _camera->vp.x, _camera->vp.y, 0,
                  _camera->vu.x,_camera->vu.y,_camera->vu.z);
    }
//    drawMainSphere();
//    drawSpheres();
//    drawSquare( -4, 0, -4 );
//    drawSquare( -4, 4, 2 );
//    drawSquare( -2, 6, -4 );
//    drawSquare( 2, -4, -4 );
//    drawSquare( -2, -2, 2 );
//    drawSquare( 2, -2, 4 );
    drawTexturedSquares();
}


void MyGLObject::drawMainSphere()
{
    this->makeCurrent();
    glPushMatrix();
    glTranslatef( _main_sphere->p.x, _main_sphere->p.y, _main_sphere->p.z );
    gluSphere( _main_sphere->qobj, 1, 100, 100 );
    glPopMatrix();
}

void MyGLObject::drawSpheres()
{
    this->makeCurrent();
    glTranslatef( 0, 0, 0 );
    for( int i = 0; i < 200; i ++ )
    {
        glPushMatrix();
        glTranslatef( _spheres[i]->p.x, _spheres[i]->p.y, _spheres[i]->p.z );
        gluSphere( _spheres[i]->qobj, 0.01, 10, 10 );
        glPopMatrix();
    }
}

void MyGLObject::drawSquare(double x, double y, double z)
{
    this->makeCurrent();
    glPushMatrix();
    glTranslatef( x, y, z );

    glBegin(GL_QUADS);									// Draw A Quad
            glColor3f(0.0f,1.0,0.0f);						// Set The Color To Green
            glNormal3f( 0, 1, 0 );
            glVertex3f( SQUARE_SIZE, SQUARE_SIZE,-SQUARE_SIZE);					// Top Right Of The Quad (Top)
            glVertex3f(-SQUARE_SIZE, SQUARE_SIZE,-SQUARE_SIZE);					// Top Left Of The Quad (Top)
            glVertex3f(-SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);					// Bottom Left Of The Quad (Top)
            glVertex3f( SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);					// Bottom Right Of The Quad (Top)

            glNormal3f( 0, -1, 0 );
            glColor3f(1.0,0.5f,0.0f);						// Set The Color To Orange
            glVertex3f( SQUARE_SIZE,-SQUARE_SIZE, SQUARE_SIZE);					// Top Right Of The Quad (Bottom)
            glVertex3f(-SQUARE_SIZE,-SQUARE_SIZE, SQUARE_SIZE);					// Top Left Of The Quad (Bottom)
            glVertex3f(-SQUARE_SIZE,-SQUARE_SIZE,-SQUARE_SIZE);					// Bottom Left Of The Quad (Bottom)
            glVertex3f( SQUARE_SIZE,-SQUARE_SIZE,-SQUARE_SIZE);					// Bottom Right Of The Quad (Bottom)

            glNormal3f( 0, 0, 1 );
            glColor3f(1.0,0.0f,0.0f);						// Set The Color To Red
            glVertex3f( SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);					// Top Right Of The Quad (Front)
            glVertex3f(-SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);					// Top Left Of The Quad (Front)
            glVertex3f(-SQUARE_SIZE,-SQUARE_SIZE, SQUARE_SIZE);					// Bottom Left Of The Quad (Front)
            glVertex3f( SQUARE_SIZE,-SQUARE_SIZE, SQUARE_SIZE);					// Bottom Right Of The Quad (Front)

            glNormal3f( 0, 0, -1 );
            glColor3f(1,1,0.0f);						// Set The Color To Yellow
            glVertex3f( SQUARE_SIZE,-SQUARE_SIZE,-SQUARE_SIZE);					// Top Right Of The Quad (Back)
            glVertex3f(-SQUARE_SIZE,-SQUARE_SIZE,-SQUARE_SIZE);					// Top Left Of The Quad (Back)
            glVertex3f(-SQUARE_SIZE, SQUARE_SIZE,-SQUARE_SIZE);					// Bottom Left Of The Quad (Back)
            glVertex3f( SQUARE_SIZE, SQUARE_SIZE,-SQUARE_SIZE);					// Bottom Right Of The Quad (Back)

            glNormal3f( -1, 0, 0 );
            glColor3f(0.0f,0.0f,1);						// Set The Color To Blue
            glVertex3f(-SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);					// Top Right Of The Quad (Left)
            glVertex3f(-SQUARE_SIZE, SQUARE_SIZE,-SQUARE_SIZE);					// Top Left Of The Quad (Left)
            glVertex3f(-SQUARE_SIZE,-SQUARE_SIZE,-SQUARE_SIZE);					// Bottom Left Of The Quad (Left)
            glVertex3f(-SQUARE_SIZE,-SQUARE_SIZE, SQUARE_SIZE);					// Bottom Right Of The Quad (Left)

            glNormal3f( 1, 0, 0 );
            glColor3f(1,0.0f,1);						// Set The Color To Violet
            glVertex3f( SQUARE_SIZE, SQUARE_SIZE,-SQUARE_SIZE);					// Top Right Of The Quad (Right)
            glVertex3f( SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE);					// Top Left Of The Quad (Right)
            glVertex3f( SQUARE_SIZE,-SQUARE_SIZE, SQUARE_SIZE);					// Bottom Left Of The Quad (Right)
            glVertex3f( SQUARE_SIZE,-SQUARE_SIZE,-SQUARE_SIZE);					// Bottom Right Of The Quad (Right)
    glEnd();
    glPopMatrix();
}

void MyGLObject::loadGLTexture( QString file, int id )
{

  QImage t;
  QImage b;

  if ( !b.load( file ) )
  {
      qDebug() << "texture not found";
      exit(-1);
  }

  t = QGLWidget::convertToGLFormat( b );
  glGenTextures( 1, &texture[id] );
  glBindTexture( GL_TEXTURE_2D, texture[id] );
  glTexImage2D( GL_TEXTURE_2D, 0, 3, t.width(), t.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, t.bits() );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
}


void MyGLObject::loadGLTextures(  )
{
    loadGLTexture( tr( CM_STRING0 ), 0 );
    loadGLTexture( tr( CM_STRING1 ), 1 );
    loadGLTexture( tr( CM_STRING2 ), 2 );
    loadGLTexture( tr( CM_STRING3 ), 3 );
    loadGLTexture( tr( CM_STRING4 ), 4 );
    loadGLTexture( tr( CM_STRING5 ), 5 );
    loadGLTexture( tr( CM_STRING6 ), 6 );
    loadGLTexture( tr( CM_STRING7 ), 7 );
    loadGLTexture( tr( CM_STRING8 ), 8 );
}



/*
    Draw a textured square plane in the given position

    It will drawn the square at ( 0, 0, 0 ) on the current matrix
        vertices list:
            (   TEXSQAURE_SIZE,   TEXSQAURE_SIZE, 0 )
            ( - TEXSQAURE_SIZE,   TEXSQAURE_SIZE, 0 )
            ( - TEXSQAURE_SIZE, - TEXSQAURE_SIZE, 0 )
            (   TEXSQAURE_SIZE, - TEXSQAURE_SIZE, 0 )
*/
void MyGLObject::drawTexturedSquare(double x, double y, double z)
{
    this->makeCurrent();
    glPushMatrix();
    glTranslatef( x, y, z );
    glBegin(GL_QUADS);
        glNormal3f( 0, 0, 1 );
        glTexCoord2f(1.0f, 1.0f);glVertex3f(   TEXSQAURE_SIZE,   TEXSQAURE_SIZE, 0 );
        glTexCoord2f(0.0f, 1.0f);glVertex3f( - TEXSQAURE_SIZE,   TEXSQAURE_SIZE, 0 );
        glTexCoord2f(0.0f, 0.0f);glVertex3f( - TEXSQAURE_SIZE, - TEXSQAURE_SIZE, 0 );
        glTexCoord2f(1.0f, 0.0f);glVertex3f(   TEXSQAURE_SIZE, - TEXSQAURE_SIZE, 0 );
    glEnd();
    glPopMatrix();
}


//draw a group of textured squares from the centre
void MyGLObject::drawTexturedSquares()
{
    this->makeCurrent();
    glPushMatrix();
    int counter = 0;
        for( int y = -1; y < 2; y ++ )
        {
            for( int x = - 1; x < 2; x ++ )
            {
                glBindTexture( GL_TEXTURE_2D, texture[counter] );
                drawTexturedSquare( x * TEXSQAURE_SIZE * 2, y * TEXSQAURE_SIZE * 2, 0 );
                counter ++;
            }
        }
    glPopMatrix();
}

void MyGLObject::keyPressEvent( QKeyEvent * event )
{
    this->makeCurrent();

    if( !ORTHO_PROJECTION )
    {
        switch( event->key() )
        {
        case Qt::Key_Left:
            Utility::RotateY( _camera, 0.05 );
            break;
        case Qt::Key_Right:
            Utility::RotateY( _camera, -0.05 );
            break;
        default:
            return;
        }
    }
    else
    {
        switch( event->key() )
        {
        case Qt::Key_Left:
            _camera->vp.x -= 0.5;
            break;
        case Qt::Key_Right:
            _camera->vp.x += 0.5;
            break;
        case Qt::Key_Up:
            _camera->vp.y -= 0.5;
            break;
        case Qt::Key_Down:
            _camera->vp.y += 0.5;
            break;
        default:
            return;
        }
    }
    this->updateGL();
    Utility::PrintCamera( _camera );
    emit cameraChanged();
}


void MyGLObject::viewpointChange( int x, int y, int w )
{
    this->makeCurrent();
    this->updateGL();
}

//change the value from f1 to f2 gradully
float MyGLObject::Change( float f1, float f2, float step )
{
    float k = f2 - f1;
    if( qAbs( k ) < 0.01 )
        return f1 - 0.01;

    if( k > 5 )
        k = 5;
    else if( k < -5 )
        k = -5;

    k = qAbs( k );

    if ( f1 > f2 )
    {
        if( f1 - k * step < MIN_VALUE )
            return MIN_VALUE;
        else
            return f1 - k * step;
    }
    else
    {
        if( f1 + k * step > MAX_VALUE )
            return MAX_VALUE;
        else
            return f1 + k * step;
    }
}
